package com.rainyun.rcmc.bukkit.config;

import com.google.common.io.ByteStreams;
import com.rainyun.rc.common.VersionException;
import com.rainyun.rc.common.VersionUtil;
import com.rainyun.rc.config.ErrorHandler;
import com.rainyun.rc.config.NodeConfig;
import com.rainyun.rc.config.annotation.Desc;
import com.rainyun.rc.util.RainFile;
import com.rainyun.rc.util.RainLoader;
import com.rainyun.rc.util.RainStr;
import org.apache.commons.lang.StringUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;

public class BukYmlNode implements NodeConfig<YamlConfiguration> {
	
	String root = "";
	String from = "";
	String suffix = ".yml";
	
	Map<String, Map<String, Object>> DescTree = new LinkedHashMap<>();
	Set<String> isUnDescSet = new HashSet<>();
	Set<String> isUnVerSet = new HashSet<>();
	ClassLoader classloader;
	ErrorHandler err_handler;
	
	Map<String,String> paths = new HashMap<String, String>();
	Map<String,Object> caches = new HashMap<String, Object>();
	Map<Class<? extends Enum<?>>,String> bindEnums = new HashMap<Class<? extends Enum<?>>,String>();
	Set<String> isResourceSet = new HashSet<>();
	Set<String> isFromEnumSet = new HashSet<>();
	
	/**创建一个配置管理器,参数:根目录,jar内文件源根目录,插件实例*/
	public BukYmlNode(String root,String from,ClassLoader classloader) {
		this.root = root;
		this.from = from;
		this.classloader = classloader;
	}
	/**创建一个配置管理器,参数:根目录,jar内文件源根目录,插件实例*/
	public BukYmlNode(File root,String from,ClassLoader classloader) {
		this.root = root.getAbsolutePath();
		this.from = from;
		this.classloader = classloader;
	}

	@Override
	public void load(String name) throws Exception{
		String path = paths.get(name);
		boolean isMulti = path.endsWith("/");
		boolean isResource = isResource(name);
		File file = new File(root,(isMulti||isResource)?path:(path+suffix));
		if(isResource) {
			caches.put(name,file);
			return;
		}else if(isMulti) {
			Map<String,Object> fm = new HashMap<String,Object>();
			Map<String,Object> dmm = new HashMap<>();
			for(File f:file.listFiles()) {
				String fname = RainStr.end(f.getName(),suffix);
				if(fname==null)continue;
				YamlConfiguration yml = new YamlConfiguration();
				try {
					yml.load(file);
				}catch (Exception e) {
					handleError(path+fname,e);
					throw e;
				}
				String filever = yml.getString("config_version");
				try {
					boolean verCheck = isVerCheck(name);
					String oldfilever = null;
					if(verCheck){
						InputStream oldin = classloader.getResourceAsStream(from+path+f.getName());
						if(oldin!=null) {
							String oldconf = new String(ByteStreams.toByteArray(oldin),StandardCharsets.UTF_8);
							YamlConfiguration oldyml = new YamlConfiguration();
							oldyml.loadFromString(oldconf);
							oldfilever = oldyml.getString("config_version");
						}
					}

					int re = VersionUtil.compareToVer(oldfilever,filever);
					if(Math.abs(re)!=1||!verCheck) {
						if(isDesc(name)) {
							Map<String, Object> dm = loadDescTree(file);
							dmm.put(fname,dm);
						}
						fm.put(fname,yml);
					}else {
						VersionException e = new VersionException(oldfilever, filever);
						handleError(path+fname,e);
						throw e;
					}
				} catch (IOException | InvalidConfigurationException e) {
					handleError(path+fname, e);throw e;
				}
			}
			if(isDesc(name)) {
				DescTree.put(name,dmm);
			}
			caches.put(name,fm);
			return;
		}else if(RainStr.end(file.getName(),suffix)!=null){
			YamlConfiguration yml = new YamlConfiguration();
			try {
				yml.load(file);
			}catch (InvalidConfigurationException e) {
				handleError(path,e);
				throw e;
			}
			String filever = yml.getString("config_version");
			try {
				boolean verCheck = isVerCheck(name);
				String oldfilever = null;

				if(verCheck)
				if(isFromEnum(name)) {
					for(Entry<Class<? extends Enum<?>>, String> en:bindEnums.entrySet()) {
						if(en.getValue().equals(name)) {
							oldfilever = VersionUtil.getVersion(en.getKey());break;
						}
					}
				}else {
					InputStream oldin = classloader.getResourceAsStream(from+path+suffix);
					if(oldin!=null) {
						String oldconf = new String(ByteStreams.toByteArray(oldin),StandardCharsets.UTF_8);
						YamlConfiguration oldyml = new YamlConfiguration();
						oldyml.loadFromString(oldconf);
						oldfilever = oldyml.getString("config_version");
					}
				}

				int re = VersionUtil.compareToVer(oldfilever,filever);
				if(Math.abs(re)!=1||!verCheck) {
					if(isDesc(name)) {
						Map<String, Object> dm = loadDescTree(file);
						DescTree.put(name,dm);
					}
					caches.put(name,yml);
					return;
				}else {
					VersionException e = new VersionException(oldfilever, filever);
					handleError(path,e);throw e;
				}
			} catch (InvalidConfigurationException | IOException e) {
				handleError(path, e);throw e;
			}
		}
	}

	@Override
	public void loadOrNew(String name) throws Exception {
		String path = paths.get(name);
		ErrorHandler oh = err_handler;
		try {
			err_handler = (a,b)->{};;
			load(name);
		} catch (VersionException e) {
			throw e;
		} catch (Exception e){
			err_handler = oh;
			if(isFromEnum(name)) {
				Class<? extends Enum<?>> bindenum = null;
				for(Entry<Class<? extends Enum<?>>, String> en:bindEnums.entrySet()) {
					if(en.getValue().equals(name)) {
						bindenum = en.getKey();break;
					}
				}
				if(bindenum!=null) {
					YamlConfiguration conf = getTreeByEnum(bindenum);
					Map<String, Object> dm = getDescTreeByEnum(bindenum);
					String oldver = VersionUtil.getVersion(bindenum);
					if(oldver!=null)conf.set("config_version",oldver);
					try {
						saveConfWithDesc(dm, conf,new File(root,path+suffix));
					} catch (IOException e1) {
						handleError(path, e1);throw e1;
					}
				}
			}else
				try {
					if(!path.endsWith("/")) {
						if(!isResource(name))path+=suffix;
						InputStream in = classloader.getResourceAsStream(from+path);
						if(in!=null)
							inputToFile(in,new File(root,path));
					}else {
						List<String> filepaths = RainLoader.getResourceFiles(from+path,classloader);
						for(String fp:filepaths) {
							InputStream in = classloader.getResourceAsStream(fp);
							if(in!=null) {
								String filename = fp.substring(from.length(),fp.length());
								inputToFile(in,new File(root,filename));
							}
						}
					}
				} catch (IOException e1) {
					handleError(path, e1);throw e1;
				}
			load(name);
		}finally {
			err_handler = oh;
		}
	}

	@Override
	public String getRootPath() {
		return this.root;
	}
	@Override
	public String getFromPath() {
		return this.from;
	}
	@Override
	public void setRootPath(String root) {
		this.root = root;
	}
	@Override
	public void setFromPath(String from) {
		this.from = from;
	}
	
	@Override
	public NodeConfig<YamlConfiguration> add(String name, String path) {
		paths.put(name, path);
		return this;
	}
	@Override
	public NodeConfig<YamlConfiguration> add(String name,String path,Class<? extends Enum<?>> e) {
		paths.put(name, path);
		bindEnums.put(e,name);
		return this;
	}
	@Override
	public void setPaths(Map<String,String> paths) {
		this.paths = paths;
	}
	@Override
	public Map<String,String> getPaths() {
		return paths;
	}
	
	@Override
	public void save(String name) throws IOException{
		Object obj = caches.get(name);
		if(obj instanceof Map) {
			Map<String, YamlConfiguration> m = (Map<String,YamlConfiguration>)obj;
			Map<String, Object> dmm = (Map<String,Object>)DescTree.get(name);
			for(Entry<String, YamlConfiguration> en:m.entrySet()) {
				YamlConfiguration yml = en.getValue();
				try {
					File f = new File(root+"/"+paths.get(name),en.getKey()+suffix);
					if(isDesc(name)&&dmm!=null) {
						Map<String, Object> dm = (Map<String, Object>) dmm.get(en.getKey());
						if(dm!=null)
							saveConfWithDesc(dm,yml,f);
						else yml.save(f);
					}else yml.save(f);
				} catch (IOException e) {
					handleError(paths.get(name)+en.getKey(), e);throw e;
				}
			}
			File fl = new File(root+"/"+paths.get(name));//删除多余的文件
			for(File f:fl.listFiles()) {
				if(!m.containsKey(RainStr.end(f.getName(),suffix)))
					f.delete();
			}
		}else {
			YamlConfiguration yml = (YamlConfiguration)obj;
			try {
				File f = new File(root,paths.get(name)+suffix);
				if(isDesc(name)) {
					Map<String, Object> dm = DescTree.get(name);
					if(dm!=null)
						saveConfWithDesc(dm,yml,f);
					else yml.save(f);
				}else yml.save(f);
			} catch (IOException e) {
				handleError(paths.get(name), e);throw e;
			}
		}
	}
	
	@Override
	public void loadAll() throws Exception {
		for(String name:paths.keySet()) {
			load(name);
		}
	}

	@Override
	public void loadAllOrNew() throws Exception {
		for(String name:paths.keySet()) {
			loadOrNew(name);
		}
	}

	@Override
	public void saveInFolder(String name, String sub_name) throws IOException {
		Map<String, YamlConfiguration> m = (Map<String,YamlConfiguration>)caches.get(name);
		YamlConfiguration yml = m.get(sub_name);
		File f = new File(root+"/"+paths.get(name),sub_name+suffix);
		if(yml==null) {
			f.delete();
			return;
		}
		try {
			boolean over = true;
			if(isDesc(name)) {
				Map<String, Object> dmm = DescTree.get(name);
				if(dmm!=null) {
					Map<String, Object> dm = (Map<String, Object>) dmm.get(sub_name);
					if(dm!=null) {
						over = false;
						saveConfWithDesc(dm,yml,f);
					}
				}
			}
			if(over)yml.save(f);
		} catch (IOException e) {
			handleError(paths.get(name)+sub_name, e);
			throw e;
		}
	}

	@Override
	public void saveAll() throws IOException {
		for(String name:paths.keySet()) {
			save(name);
		}
	}

	@Override
	public Map<String, YamlConfiguration> getFolder(String name) {
		return (Map<String, YamlConfiguration>) get(name);
	}
	
	@Override
	public YamlConfiguration get(String name) {
		return (YamlConfiguration) caches.get(name);
	}
	@Override
	public void set(String name,Object obj) {
		caches.put(name,obj);
	}
	
	@Override
	public void setErrorHandler(ErrorHandler handler) {
		this.err_handler = handler;
	}
	
	public void handleError(String path, Exception e) {
		if(err_handler!=null) {
			err_handler.handleError(path, e);
		}else e.printStackTrace();
	}

	@Override
	public NodeConfig<YamlConfiguration> bindEnum(String name,Class<? extends Enum<?>> e) {
		bindEnums.put(e, name);
		return this;
	}

	@Override
	public <V> V get(Enum<?> e) {
		String name = bindEnums.get(e.getClass());
		if(name!=null) {
			YamlConfiguration yml = (YamlConfiguration) caches.get(name);
			String nodes = e.toString().toLowerCase().replace("_",".");
			return (V) yml.get(nodes);
		}
		return null;
	}
	@Override
	public void set(Enum<?> e,Object val) {
		String name = bindEnums.get(e.getClass());
		if(name!=null) {
			YamlConfiguration yml = (YamlConfiguration) caches.get(name);
			String nodes = e.toString().toLowerCase().replace("_",".");
			yml.set(nodes,val);
		}
	}
	
	@Override	
	public YamlConfiguration getTreeByEnum(Class<? extends Enum<?>> ec) {
		Field[] fs = ec.getDeclaredFields();
		YamlConfiguration yml = new YamlConfiguration();
		yml.set("config_version",VersionUtil.getVersion(ec));
		for(Field f:fs) {
			f.setAccessible(true);
			String name = f.getName().toLowerCase().replace("_",".");
			try {
				Object en = f.get(null);
				if(!(en instanceof Enum<?>))continue;
				Object def = NodeConfig.getDefaultByEnum((Enum<?>)en);
				yml.set(name,def);
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return yml;
	}
	
//	@Override
	public Map<String,Object> getDescTreeByEnum(Class<? extends Enum<?>> ec) {
		Field[] fs = ec.getDeclaredFields();
		YamlConfiguration yml = new YamlConfiguration();
		for(Field f:fs) {
			f.setAccessible(true);
			String name = f.getName().toLowerCase().replace("_",".");
			try {
				Object en = f.get(null);
				if(!(en instanceof Enum<?>))continue;
				Desc desc = f.getAnnotation(Desc.class);
				if(desc!=null) {
					List<String> list = new ArrayList<>();
					for(String str:desc.value().split("\n"))list.add("#"+str);
					yml.set(name+"_desc",list);
				}
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return (Map<String, Object>) new Yaml().load(yml.saveToString());
	}
	
	@Override
	public Map<String, Object> getDescTree(String name) {
		return DescTree.get(name);
	}
	@Override
	public void setDescTree(String name, Map<String,Object> tree) {
		DescTree.put(name,tree);
	}
	@Override
	public void setDesc(String name,boolean b) {
		if(!b)isUnDescSet.add(name);
		else isUnDescSet.remove(name);
	}
	@Override
	public boolean isDesc(String name) {
		return !isUnDescSet.contains(name);
	}
	@Override
	public void setVerCheck(String name,boolean b) {
		if(!b)isUnVerSet.add(name);
		else isUnVerSet.remove(name);
	}
	@Override
	public boolean isVerCheck(String name) {
		return !isUnVerSet.contains(name);
	}

	@Override
	public void setResource(String name, boolean b) {
		if(b)isResourceSet.add(name);
		else isResourceSet.remove(name);
	}
	@Override
	public boolean isResource(String name) {
		return isResourceSet.contains(name);
	}
	
	/**(常用)获取配置文件(单文件)的注释配置树
	 * @throws IOException */
	public static Map<String,Object> loadDescTree(File file) throws IOException {
		InputStreamReader isr = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
		BufferedReader r = new BufferedReader(isr);
		StringBuilder b = new StringBuilder();
		String line;
		String trim;
		String tmpkey;
		List<String> desc = new ArrayList<String>();
		while((line=r.readLine())!=null) {
			trim = line.trim();
			trim = trim.isEmpty()?"#":trim;
			if(trim.startsWith("#")) {
				desc.add(trim.substring(trim.indexOf("#")));
				tmpkey = null;
			}else {
				if(!desc.isEmpty()) {
					tmpkey = trim;
					if(!tmpkey.isEmpty()&&tmpkey.contains(":")){
						int space = 0;
						for(char c:line.toCharArray())
							if(c==' ')space++;
							else break;
						tmpkey = tmpkey.substring(0,tmpkey.lastIndexOf(":"));
						String spacestr = StringUtils.repeat(" ", space);
						b.append(spacestr+tmpkey+"_desc:\r\n");
						for(String des:desc) {
							b.append(spacestr+"- \""+des+"\"");
							b.append("\r\n");
						}
						desc.clear();
					}
				}
				b.append(line);
				b.append("\r\n");
			}
		}
		r.close();
		Yaml yml = new Yaml();
		Map<String,Object> map = (Map<String, Object>) yml.load(b.toString());
		return NodeConfig.getDescTree(map);
	}
	
	/**(常用)保存配置文件(带注释)
	 * @param tree 注释配置树
	 * @param conf 配置对象
	 * @param f 文件*/
	public static void saveConfWithDesc(Map<String,Object> tree,YamlConfiguration conf,File f) throws IOException {
		Map<String, Object> dm = tree;
		conf = mergeDescTree(dm,conf);
		String str = confWithDescTreeToString(conf);
		saveStrToFile(str,f);
	}
	
	/**保存字符串到文件*/
	public static void saveStrToFile(String str,File f) throws IOException {
		File fl = f.getParentFile();
		if(!fl.exists())fl.mkdirs();
		if(!f.exists()) f.createNewFile();
		FileOutputStream fo = new FileOutputStream(f,false);
		fo.write(str.getBytes(StandardCharsets.UTF_8));fo.close();
	}
	/**输入流直接保存为文件*/
	public static void inputToFile(InputStream in,File f) throws IOException {
		RainFile.newFile(f);
		OutputStream out = new FileOutputStream(f);
		ByteStreams.copy(in,out);
        out.close();
        in.close();
	}
	
	/**获取配置和注释配置树合并后的新配置,以用于descTreeToString*/
	public static YamlConfiguration mergeDescTree(Map<String,Object> tree,MemorySection conf) {
		YamlConfiguration newyml = new YamlConfiguration();
		for(String key:conf.getKeys(false)) {
			Object value = conf.get(key);
			Object desc = tree.get(key+"_desc");
			if(desc!=null) {
				newyml.set(key+"_desc",desc);
			}
			if(value instanceof MemorySection) {
				Object descMap = tree.get(key);
				if(descMap!=null) {
					value = mergeDescTree((Map<String, Object>) descMap,(MemorySection)value);
				}
			}
			newyml.set(key,value);
		}
		return newyml;
	}
	/**和注释配置树合并后的配置转字符串*/
	public static String confWithDescTreeToString(YamlConfiguration conf) {
		String strconf = conf.saveToString();
		List<String> strlist = new ArrayList<String>();
		String[] stra = RainStr.split(strconf,"\n",-1);
		String trim;
		boolean descnow=false;
		for(String str:stra) {
			trim=str.trim();
			if(trim.endsWith("_desc:")) {
				descnow = true;
			}else {
				if(descnow){
					if(trim.startsWith("-")) {
						int space = 0;
						for(char c:str.toCharArray())
							if(c==' ')space++;
							else break;
						String spacestr = StringUtils.repeat(" ", space);
						String desc = trim.substring(3,trim.length()-1);
						strlist.add(spacestr+(desc.equals("#")?"":desc));
					}else descnow=false;
				}
				if(!descnow)strlist.add(str);
			}
		}
		return String.join("\n",strlist);
	}

	/**配置节点转Map*/
	public static Map<String,Object> confToMap(ConfigurationSection conf){
		YamlConfiguration yml = new YamlConfiguration();
		yml.set("tmp",conf);
		Yaml yaml = new Yaml();
		Object tm = yaml.load(yml.saveToString());
		if(tm==null)return new LinkedHashMap<>();
		return (Map<String, Object>) ((Map<String,Object>)tm).get("tmp");
	}

	@Override
	public File getResource(String name) {
		return (File) caches.get(name);
	}
	
	@Override
	public void setFromEnum(String name, boolean b) {
		if(b)isFromEnumSet.add(name);
		else isFromEnumSet.remove(name);
	}
	@Override
	public boolean isFromEnum(String name) {
		return isFromEnumSet.contains(name);
	}
}
